﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using MathematicallySafe.HeadCounter.Data.Common;
using MathematicallySafe.HeadCounter.Data.Interfaces;
using StructureMap;

namespace MathematicallySafe.HeadCounter.Data.Models
{
    public enum AttendanceLevel
    {
        High,
        Good,
        Fair,
        Low
    }

    [Serializable]
    public class Raider
    {
        //private IOptions Options = ObjectFactory.GetInstance<IOptions>();
        //private static readonly IOptions Options = new OptionsModel();

        private static IOptions _options;
        private static IOptions Options
        {
            get
            {
                if (_options == null)
                {
                    _options = ObjectFactory.GetInstance<IOptions>();
                }

                return _options;
            }
        }

        private static IGuild _guild;
        private static IGuild Guild
        {
            get
            {
                if (_guild == null)
                {
                    _guild = ObjectFactory.GetInstance<IGuild>();
                }

                return _guild;
            }
        }

        private static ILootTable _lootTable;
        private static ILootTable LootTable
        {
            get
            {
                if (_lootTable == null)
                {
                    _lootTable = ObjectFactory.GetInstance<ILootTable>();
                }

                return _lootTable;
            }
        }

        private Character _main;
        private List<GearItem> _gearItems;
        private readonly object _sync = new object();
        public bool IsInactive;

        public Raider() { }

        public Raider(Character character)
        {
            Identity = character.Name;
            Characters = new List<Character>();
            GearItems = new List<GearItem>();
            AddCharacter(character);
            SetMain(character);
        }

        // The character name we first saw for this raider and used as their identity
        // regardless of rerolls, alts, etc.
        public string Identity { get; set; }

        public DateTime Joined { get; set; }

        public string MainName { get; set; }
        public List<Character> Characters { get; set; }

        public string Name
        {
            get { return GetMain().Name; }
        }

        public CharacterClass Class
        {
            get { return GetMain().Class; }
        }

        public bool IsShieldTank { get; set; }
        public bool IsDualWield { get; set; }

        public CharacterRace Race
        {
            get { return GetMain().Race; }
        }

        #region Main

        public void SetMain(Character character)
        {
            SetMain(character.Name);
        }

        public void SetMain(string characterName)
        {
            MainName = characterName;
        }

        private Character GetMain()
        {
            if (_main == null)
            {
                _main = Characters.FirstOrDefault(c => c.Name == MainName);

                if (_main == null)
                {
                    throw new KeyNotFoundException("Current main name " + MainName +
                                                   " not found in characters collection for " + Identity);
                }
            }

            return _main;
        }

        #endregion

        #region Characters

        public void AddCharacter(string name, CharacterClass characterClass, CharacterRace characterRace)
        {
            AddCharacter(new Character
                         {
                             Name = name,
                             Class = characterClass,
                             Race = characterRace
                         });
        }

        public void AddCharacter(Character character)
        {
            if (!HasCharacter(character))
            {
                Characters.Add(character);
            }
            else
            {
                throw new ArgumentException("Character " + character.Name + " already exists for raider " + Identity);
            }
        }

        public bool HasCharacter(Character character)
        {
            return HasCharacter(character.Name);
        }

        public bool HasCharacter(string characterName)
        {
            return Characters.FirstOrDefault(c => c.Name == characterName) != null;
        }

        #endregion

        #region Characters & Mains

        //        private string _characterList = null;
        //
        //        public string CharacterList
        //        {
        //            get
        //            {
        //                if (_characterList == null)
        //                {
        //                    GenerateAltList();
        //                }
        //
        //                return _characterList;
        //            }
        //        }
        //
        //        private void GenerateAltList()
        //        {
        //            _characterList = "";
        //
        //            Characters.ForEach(alt => _characterList += alt.Name + ", ");
        //
        //            int n = _characterList.LastIndexOf(",");
        //            if (n > 0)
        //            {
        //                _characterList = _characterList.Substring(0, n);
        //            }
        //        }
        //
        //            if (Characters.FirstOrDefault(a => a.Name == this.Name) == null)
        //            {
        //                // Make sure the original main is stored as an alt.
        //                Characters.Add(Main);
        //            }
        //
        //            Main = Characters.FirstOrDefault(a => a.Name == alt);
        //        }

        //        public bool AnyNameMatch(string name)
        //        {
        //            return (Identity == name || CharacterList.Contains(name));
        //        }

        #endregion

        #region Gear

        // todo this should all be factored out into GearManager or something.

        public List<GearItem> GearItems
        {
            get
            {
                if (_gearItems == null)
                {
                    lock (_sync)
                    {
                        _gearItems = new List<GearItem>();
                    }
                }

                return _gearItems;
            }

            set
            {
                lock (_sync)
                {
                    _gearItems = value;
                }
            }
        }

        public bool HasDownloadedItems()
        {
            return _gearItems != null && _gearItems.Count() > 0;
        }

        public IEnumerable<LootItem> EquippedLoot
        {
            get
            {
                IEnumerable<LootItem> snapshot;

                lock (_sync)
                {
                    if (_gearItems == null) return null;

                    var loot = from i in _gearItems
                               join l in LootTable.Items on i.ID equals l.ID
                               where i.Equipped
                               select l;

                    snapshot = loot.ToList();
                }

                return snapshot;
            }
        }

        public IEnumerable<LootItem> OwnedLoot
        {
            get
            {
                IEnumerable<LootItem> snapshot;

                lock (_sync)
                {
                    if (_gearItems == null) return null;

                    var loot = from i in _gearItems
                               join l in LootTable.Items on i.ID equals l.ID
                               select l;

                    snapshot = loot.ToList();
                }

                return snapshot;
            }
        }

        public bool HasGear(int id)
        {
            return GearItems.FirstOrDefault(g => g.ID == id) != null;
        }

        public void AddGear(int id, bool fromRaid)
        {
            var gearItem = new GearItem()
                           {
                               ID = id,
                               FromRaid = fromRaid,
                           };

            lock(_sync)
            {
                if (!HasGearItem(gearItem))
                {
                    _gearItems.Add(gearItem);
                }
                else
                {
                    if (gearItem.Equipped) return;
                }

                var lootItem = LootTable[id];
                if (lootItem != null)
                {
                    FindBestItem(lootItem.Slot, gearItem);
                }
            }
        }

        public void RemoveGear(int id)
        {
            lock (_sync)
            {
                var currentGear = GearItems.FirstOrDefault(g => g.ID == id);

                if (currentGear != null)
                {
                    _gearItems.Remove(currentGear);

                    var currentLoot = LootTable[currentGear.ID];
                    if (currentLoot != null)
                    {
                        FindBestItem(currentLoot.Slot);
                    }
                }
            }
        }

        private bool HasGearItem(GearItem gearItem)
        {
            return GearItems.Count(g => g.ID == gearItem.ID) > 0;
        }

        private bool CanAddItem(LootItem lootItem)
        {
            bool canAddItem;
            int count = GearItems.Count(g => g.ID == lootItem.ID);

            if (count > 0)
            {
                canAddItem = CanEquipTwo(lootItem.Slot) && !lootItem.Unique;
            }
            else
            {
                canAddItem = true;
            }

            return canAddItem;
        }

        private bool CanEquipTwo(LootSlot slot)
        {
            return slot == LootSlot.Finger || slot == LootSlot.OneHand || slot == LootSlot.Trinket;
        }

        private void FindBestItem(LootSlot slot)
        {
            FindBestItem(slot, null);
        }

        private void FindBestItem(LootSlot slot, GearItem latestGear)
        {
            var latestItem = latestGear != null ? LootTable[latestGear.ID] : null;

            var gearThisSlot = from g in GearItems
                               join l in LootTable.Items on g.ID equals l.ID
                               where l.Slot == slot && !g.Offspec
                               select g;

            var count = gearThisSlot.Count();
            var countEquipped = gearThisSlot.Where(g => g.Equipped).Count();

            if (count == 1)
            {
                // One item definitely equipped
                gearThisSlot.Single().Equipped = true;
                CheckForTwoHanderUnequips(slot);
            }
            else if (CanEquipTwo(slot) && count == 2)
            {
                // Two items and can equip two also definitely equipped.
                gearThisSlot.ForEach(g => g.Equipped = true);
            }
            else if (latestGear != null)
            {
                // Looted in a raid assume they equip this
                if (CanEquipTwo(slot))
                {
                    if (countEquipped == 1)
                    {
                        latestGear.Equipped = true;
                    }
                    else if (countEquipped > 1)
                    {
                        UnequipAll(gearThisSlot);

                        // This will ultimately be something of a guess
                        var gearFromRaid = gearThisSlot.Where(g => g.FromRaid && g.ID != latestGear.ID);

                        if (gearFromRaid.Count() > 0)
                        {
                            gearFromRaid.First().Equipped = true;
                        }
                        else
                        {
                            gearThisSlot.Where(g => g.ID != latestGear.ID).First().Equipped = true;
                        }
                    }

                    latestGear.Equipped = true;
                }
                else
                {
                    UnequipAll(gearThisSlot);
                    CheckForTwoHanderUnequips(slot);
                    latestGear.Equipped = true;
                }
            }
            else
            {
                // todo work out the best item in more complex cases.
            }
        }

        private void CheckForTwoHanderUnequips(LootSlot slot)
        {
            if (slot == LootSlot.TwoHand)
            {
                // Two hander equipped, so make sure all one hand and offhand are unequipped
                UnequipAll(LootSlot.OneHand);
                UnequipAll(LootSlot.MainHand);
                UnequipAll(LootSlot.OffHand);
            }
        }

        private void UnequipAll(IEnumerable<GearItem> gearThisSlot)
        {
            gearThisSlot.ForEach(g => g.Equipped = false);
        }

        private void UnequipAll(LootSlot slot)
        {
            var gearThisSlot = from g in GearItems
                               join l in LootTable.Items on g.ID equals l.ID
                               where l.Slot == slot
                               select g;

            gearThisSlot.ForEach(g => g.Equipped = false);
        }

        public void AutoEquip(LootItem item)
        {
            FindBestItem(item.Slot, _gearItems.Where(g => g.ID == item.ID).FirstOrDefault());
        }

        public void AutoEquip(LootSlot slot)
        {
            FindBestItem(slot, null);
        }

        #endregion

        #region Gear Metrics

        public double AverageILevel { get; set; }
        public double GearScore { get; set; }
        public int ProgressionItemsCount { get; set; }
        public int LowLevelItemsCount { get; set; }

        #endregion

        #region Attendance & Loot metrics

        public double ShortPeriodAllAttendance { get; set; }
        public double ShortPeriodAttendance { get; set; }
        public double LifetimeAttendance { get; set; }

        public double LongPeriodAttendance { get; set; }
        public double LongPeriodAllAttendance { get; set; }
        public double LifetimeAllAttendance { get; set; }

        public double ShortPeriodEffectiveAttendance { get { return Math.Max(ShortPeriodAttendance, Options.MaxEffectiveAttendancePercent / 100.0); } }
        public double LongPeriodEffectiveAttendance { get { return Math.Max(LongPeriodAttendance, Options.MaxEffectiveAttendancePercent / 100.0); } }
        public double LifetimeEffectiveAttendance { get { return Math.Max(LifetimeAttendance, Options.MaxEffectiveAttendancePercent / 100.0); } }

        public double StdDevsFromLootRateMean { get; set; }
        public double StdDevsFromWeightedLootRateMean { get; set; }

        public AttendanceLevel AttendanceLevel
        {
            get
            {
                if (ShortPeriodAttendance >= Options.HighAttendancePercentage / 100.0)
                {
                    return AttendanceLevel.High;
                }

                if (ShortPeriodAttendance >= Options.GoodAttendancePercentage / 100.0)
                {
                    return AttendanceLevel.Good;
                }

                if (ShortPeriodAttendance >= Options.FairAttendancePercentage / 100.0)
                {
                    return AttendanceLevel.Fair;
                }

                return AttendanceLevel.Low;
            }
        }

        public int ShortPeriodLootCount { get; set; }
        public int LongPeriodLootCount { get; set; }
        public int LifetimeLootCount { get; set; }

        public double ShortPeriodLootValue { get; set;}
        public double LongPeriodLootValue { get; set;}
        public double LifetimeLootValue { get; set; }

        public double ShortPeriodLootRate { get; set;}
        public double LongPeriodLootRate { get; set;}
        public double LifetimeLootRate { get; set; }

        public double ShortPeriodWeightedLootRate { get; set; }
        public double LongPeriodWeightedLootRate { get; set; }
        public double LifetimeWeightedLootRate { get; set; }

        public double? ShortPeriodLootPriority { get; set; }
        public double? LongPeriodLootPriority { get; set; }
        public double? LifetimeLootPriority { get; set; }

        public double? ShortPeriodRelativeLootPriorty
        {
            get
            {
                if (ShortPeriodLootPriority.HasValue)
                {
                    return Guild.ShortPeriodLootPriorityMean - ShortPeriodLootPriority;
                }

                return Guild.ShortPeriodLootPriorityMean;
            }
        }

        public double? LongPeriodRelativeLootPriorty
        {
            get
            {
                if (LongPeriodLootPriority.HasValue)
                {
                    return Guild.LongPeriodLootPriorityMean - LongPeriodLootPriority;
                }

                return Guild.LongPeriodLootPriorityMean;
            }
        }

        public double? LifetimeRelativeLootPriorty
        {
            get
            {
                if (LifetimeLootPriority.HasValue)
                {
                    return Guild.LifetimeLootPriorityMean - LifetimeLootPriority;
                }

                return Guild.LifetimeLootPriorityMean;
            }
        }

        #endregion

        #region Loot Won

        //private List<LootWin> _lootWin;

        //public IEnumerable<LootWin> LootWon
        //{
        //    get
        //    {
        //        //if (_lootWin == null)
        //        //{
        //        //    _lootWin = new List<LootWin>();

        //        //    ZS.RaidWeeks.Raids.ForEach(r =>
        //        //    {
        //        //        r.Loot.ForEach(l =>
        //        //        {
        //        //            if (l.Winner == this)
        //        //            {
        //        //                _lootWin.Add(new LootWin()
        //        //                {
        //        //                    RaidKey = r.Key,
        //        //                    Timestamp = l.Time,
        //        //                    Loot = l.Loot
        //        //                });
        //        //            }
        //        //        });
        //        //    });
        //        //}

        //        return _lootWin;
        //    }
        //}

        #endregion

        public string WebSafeName
        {
            get
            {
                string normal = GetMain().Name.Normalize(NormalizationForm.FormD);
                var sb = new StringBuilder(normal);

                for (int i = 0; i < sb.Length; i++)
                {
                    var uc = CharUnicodeInfo.GetUnicodeCategory(sb[i]);

                    if (CharUnicodeInfo.GetUnicodeCategory(sb[i]) == UnicodeCategory.NonSpacingMark)
                    {
                        sb.Remove(i, 1);
                    }
                }

                return sb.ToString();
            }
        }

    }
}